;;; init-python.el --- Configuration for python programming -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:

(defvar python-shell-interpreter-default "python"
  "The default python-shell-interpreter value.")
(defvar python-shell-interpreter-args-default "-i"
  "The default python-shell-interpreter-args value.")
(defvar python-shell-interpreter-args-4-ipython "--simple-prompt -i"
  "The python-shell-interpreter-args value for ipython.")

(defun ctrlwei/python-choose-version ()
  "Find python executable version in exec-path."
  (interactive)
  (let* ((versions nil)
         (python-choices (dolist (path exec-path versions)
                           (setq versions-in-path
                                 (directory-files path t "^[i]?python[0-9]?[.]?[0-9]?[m]?$"))
                           (setq versions (append versions versions-in-path))))
         (default-version (if (member "/usr/bin/python3" python-choices)
                              "/usr/bin/python3"
                            (if python-choices
                                (car python-choices)
                              python-shell-interpreter-default)))
         (python-bin (completing-read
                      (format "Python executable version (default %s): " default-version)
                      (if python-choices python-choices (list python-shell-interpreter-args-default))
                      nil
                      t
                      nil
                      nil
                      default-version)))
    python-bin))

(defun ctrlwei/python-shell-set-version (&optional reset)
  "Toggle the python-shell-interpreter to default or the value of user input."
  (interactive "P")
  (if (not (featurep 'python))
      (require 'python))
  (if reset
      (progn
        (custom-reevaluate-setting 'python-shell-interpreter)
        (custom-reevaluate-setting 'python-shell-interpreter-args)
        (message "Set python-shell-interpreter to default."))
    (progn
      (let* ((shell-version (ctrlwei/python-choose-version))
             (shell-args (if (string-match-p "ipython" shell-version)
                             python-shell-interpreter-args-4-ipython
                           python-shell-interpreter-args-default)))
        (setq python-shell-interpreter shell-version)
        (setq python-shell-interpreter-args shell-args))
      (message "Set python-shell-interpreter to %s." python-shell-interpreter))))

(defun ctrlwei/python-buffer-set-python (&optional remove-local)
  "Set python-shell-interpreter as a local variable,
so different python buffer can use different python shell."
  (interactive "P")
  (if remove-local
      (if (local-variable-p 'python-shell-interpreter)
          (progn
            (kill-local-variable 'python-shell-interpreter)
            (kill-local-variable 'python-shell-interpreter-args)
            (message "Set python-shell-interpreter to global value."))
        (message "python-shell-interpreter is not buffer local variable"))
    (let* ((shell-version (ctrlwei/python-choose-version))
           (shell-args (if (string-match-p "ipython" shell-version)
                           python-shell-interpreter-args-4-ipython
                         python-shell-interpreter-args-default)))
      (set (make-local-variable 'python-shell-interpreter) shell-version)
      (set (make-local-variable 'python-shell-interpreter-args) shell-args)
      (message "Locally set python-shell-interpreter to %s." python-shell-interpreter))))



;; FIXME: If open python shell in a python shell, there are some issues.
;; 1. the origin shell must run python-shell-font-lock-toggle to turn off
;;    font lock. if not, error occured when commit command.
;; 2. the new shell must turn off company-mode. if not, company-mode give
;;    series error.
;; 3. I just tested continuous open two shell.

(defun ctrlwei/run-python (&optional global)
  "Open python shell for buffer if GLOBAL is nil."
  (interactive "P")
  (if global
      (if (local-variable-p 'python-shell-interpreter)
          (message (concat  "python-shell-interpreter is buffer local variable, "
                            "do not use it to create global python shell process."))
        (run-python))
    (run-python (python-shell-calculate-command) t)))



(defun pyenv-executable-find (command)
  "Find executable taking pyenv shims into account.
If the executable is a system executable and not in the same path
as the pyenv version then also return nil.
This works around https://github.com/pyenv/pyenv-which-ext."
  (if (executable-find "pyenv")
      (progn
        (let ((pyenv-string (shell-command-to-string (concat "pyenv which " command)))
              (pyenv-version-names (split-string (string-trim (shell-command-to-string "pyenv version-name")) ":"))
              (executable nil)
              (i 0))
          (if (not (string-match "not found" pyenv-string))
              (while (and (not executable)
                          (< i (length pyenv-version-names)))
                (if (string-match (elt pyenv-version-names i) (string-trim pyenv-string))
                    (setq executable (string-trim pyenv-string)))
                (if (string-match (elt pyenv-version-names i) "system")
                    (setq executable (string-trim (executable-find command))))
                (setq i (1+ i))))
          executable))
    (executable-find command)))

(defun python-execute-file (arg)
  "Execute a python script in a shell."
  (interactive "P")
  ;; set compile command to buffer-file-name
  ;; universal argument put compile buffer in comint mode
  (let ((universal-argument t)
        (compile-command (format "%s %s"
                                 (pyenv-executable-find python-shell-interpreter)
                                 (shell-quote-argument (file-name-nondirectory buffer-file-name)))))
    (if arg
        (call-interactively 'compile)
      (compile compile-command t)
      (with-current-buffer (get-buffer "*compilation*")
        (inferior-python-mode)))))



(with-eval-after-load 'anaconda-mode
  )

(with-eval-after-load 'pipenv
  (setq pipenv-projectile-after-switch-function #'pipenv-projectile-after-switch-extended)
  (define-key pipenv-mode-map (kbd "C-c C-p C-p") 'ctrlwei/run-python)
  )

(with-eval-after-load 'python
  (add-hook 'python-mode-hook 'anaconda-mode)
  (add-hook 'python-mode-hook 'anaconda-eldoc-mode)
  (add-hook 'python-mode-hook 'pipenv-mode)

  (setq company-backends-python-mode (cons '(company-anaconda :with company-yasnippet) ctrlwei-default-company-backends-with-yas))
  (add-hook 'python-mode-hook (lambda ()
                                (set (make-local-variable 'company-backends) company-backends-python-mode)))
  (define-key inferior-python-mode-map (kbd "C-l") 'comint-clear-buffer))

(with-eval-after-load 'lsp-python-ms
  (setq lsp-python-ms-dir "~/sys/python-language-server")
  (setq lsp-python-ms-executable
        "~/sys/python-language-server/output/bin/Release/linux-x64/publish/Microsoft.Python.LanguageServer")
  (setq lsp-python-ms-python-executable-cmd "python3"))


(provide 'init-python)
;;; init-python.el ends here
